package sdc.net.server.appli;

import java.util.ArrayList;
import java.util.HashMap;
import sdc.net.server.*;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import sdc.Address;
import sdc.ServiceBroker;
import sdc.component.type.ComponentType;
import sdc.component.type.ComponentTypePool;
import sdc.msg.Message;
import sdc.msg.OptionGreater;
import sdc.msg.construct.AddressReplyMessage;
import sdc.msg.construct.DegreeRequestMessage;
import sdc.msg.use.*;
import sdc.net.MessageBuffer;
import sdc.net.router.Router;
import sdc.net.server.comp.ComponentServer;
import sdc.net.server.comp.ComponentServerManager;

public final class ApplicationServer extends AbstractServer
{

	/**
	 * ?f?t?H???g??A?N?Z?X??
	 */
	public static final int DEFAULT_ACCESS_NUM = 100;
	/**
	 * connection manager for managing connections with use components.
	 */
	private Map<ComponentType, Address> useComponentMap;
	/**
	 * connection manger for managing connections with search components.
	 */
	private Map<ComponentType, Address> searchComponentMap;
	/**
	 * message buffer for this server.
	 */
	private MessageBuffer messageBuffer;

	/**
	 * ????A?v???P?[?V?????T?[?o??????P???????????A?N?Z?X??
	 */
	private int accessNum;

        //final(?)
	private double mu = 1.0;


	private List<Address> failedList;

        //private Map<ComponentType, Integer> tripTimeMap;

	/**
	 * 
	 * @param address
	 * @param gatewayRouter
	 */
	public ApplicationServer(Address address, Router gatewayRouter)
	{
		this(address, gatewayRouter, DEFAULT_ACCESS_NUM);
	}

	/**
	 * @param address
	 * @param gatewayRouter
	 * @param accessNum
	 */
	public ApplicationServer(Address address, Router gatewayRouter, int accessNum)
	{
		super(address, gatewayRouter);
		this.accessNum = accessNum;
		//useConnectionManager = new ConnectionManager(address);
		//searchConnectionManager = new ConnectionManager(address);
		useComponentMap = new HashMap<ComponentType, Address>();
		searchComponentMap = new HashMap<ComponentType, Address>();
		messageBuffer = new MessageBuffer();
		failedList = new ArrayList<Address>();
                //tripTimeMap = new HashMap<ComponentType, Integer>();
	}

	/**
	 * @return the accessNum
	 */
	public int getAccessNum()
	{
		return accessNum;
	}


	public double getMu()
	{
		return mu;
	}

	public Map<ComponentType, Address> getUseComponentMap()
	{
		return this.useComponentMap;
	}

        //public Map<ComponentType, Integer> getTripTimeMap(){
        //    return this.tripTimeMap;
        //}
	/**
	 *
	 * @param useComponentNum
	 */
	public void createService(int useComponentNum){
		if (ComponentTypePool.INSTANCE.getList().size() < useComponentNum)
			throw new IllegalArgumentException("There are not so many component types in this situation.");

		for (ComponentType type : ComponentTypePool.INSTANCE.getShuffledList2(useComponentNum)){
			List<Address> candidateAddresses = ServiceBroker.INSTANCE.getPeerAddresses(type, type.getStrategy().getCandidateNumForUseComponent());
			sendServiceTimeRequest(type, candidateAddresses);
		}
	}

	/**
	 *
	 * @param type
	 * @param candidateNum
	 */
	private void sendServiceTimeRequest(ComponentType type, List<Address> addresses){
		messageBuffer.setWaitingMessageNum(addresses.size());

		for (Address destAddress : addresses){
			messageSender.sendMessage(new ServiceTimeRequestMessage(getAddress(), destAddress, type));
		}
	}

	/**
	 * decide which component to use for service.
	 * this method choose the component with the smallest service time.
	 */
	private void sendUseRequest(){
		TreeMap<Double, Message> performanceMap = new TreeMap<Double, Message>();


		for(Message message : messageBuffer.popAllMessages()){
			if(failedList.contains(message.getSrcAddress())) continue;
			performanceMap.put(message.getOption(), message);
		}


		Message message = performanceMap.get(performanceMap.lastKey());

		messageSender.sendMessage(new UseRequestMessage(getAddress(), message.getSrcAddress(), message.getType(), this.getAccessNum()));


		messageBuffer.clear();
	}

	/**
	 *
	 * @param componentType
	 * @param useAddress
	 * @param addresses
	 */
	public void useComponent(ComponentType componentType, Address useAddress, List<Address> addresses){
                ComponentServerManager csm = ComponentServerManager.INSTANCE;
		useComponentMap.put(componentType, useAddress);
                csm.get(useAddress).setEWMA(componentType, 0.0);
		addresses.remove(useAddress);
                for(int i =0;i<ComponentTypePool.INSTANCE.getTypeNum();i++){
                    double tmp = ComponentTypePool.INSTANCE.getProbVector(componentType)[i];
                    if(tmp > 0){
                        //System.out.println(componentType.getTypeID() + ":" + i);
                        csm.get(useAddress).createService(ComponentTypePool.INSTANCE.get(i));
                    }
                }
                //sendTATRequest(componentType,useAddress);
		sendDegreeRequest(componentType, addresses);
	}

        public void changeAccess(Address destComponentServer, ComponentType type) {
            useComponentMap.put(type, destComponentServer);
            ComponentServer dest = ComponentServerManager.INSTANCE.get(destComponentServer);
            if(dest.getEwma(type) == 0.0) {
                dest.setEWMA(type, 0.0);
                dest.createService(type);
            }
        }

        public void removeAccess(ComponentType type) {
            useComponentMap.remove(type);
        }

	/**
	 *
	 * @param type
	 * @param addresses
	 */
	private void sendDegreeRequest(ComponentType type, List<Address> addresses)
	{
		messageBuffer.setWaitingMessageNum(addresses.size());

		for (Address a : addresses)
		{
			messageSender.sendMessage(new DegreeRequestMessage(getAddress(), a, type));
		}
	}

	/**
	 *
	 * @param componentType
	 */
//	private void findSubstitute(ComponentType componentType)
//	{
//		Address searcherAddress = searchComponentMap.get(componentType);
//
//		if (searcherAddress == null)
//			throw new NullPointerException("There are no search components");
//
//		componentType.getStrategy().getStrategyForDecideSubstitute().decideSubstitute(this, componentType, searcherAddress);
//	}

        public void sendTATRequest(){
            
        }

        public double getTAT(){
            return 0.0;
        }

        public void clearUseComponentMap(){
            this.useComponentMap.clear();
        }

        public void setMu(double val){
            this.mu = val;
        }

	@Override
	public void doFailure()
	{
		throw new UnsupportedOperationException("Not supported yet.");
	}

	@Override
	public void doRecover()
	{
		throw new UnsupportedOperationException("Not supported yet.");
	}


	@Override
	public void receiveMessage(Message message)
	{

		if (message == null)
			throw new NullPointerException("Received message is null!");

		ComponentType type = message.getType();


		switch (message.getID())
		{
			case SERVICE_TIME_REPLY:

				if (messageBuffer.pushMessage(message) == true){
                                    //message.getOption();
                                    sendUseRequest();
                                }
				break;

			case USE_REPLY:

				List<Address> addressList = ((UseReplyMessage) message).getAddressList();
				useComponent(type, message.getSrcAddress(), addressList);
                                
				break;

			case DEGREE_REPLY:

				if (messageBuffer.pushMessage(message) == true)
				{
					messageBuffer.sortMessage(OptionGreater.INSTANCE);

					Message search = messageBuffer.popMessage();

					searchComponentMap.put(type, search.getSrcAddress());

					messageBuffer.clear();
				}
				break;

			case FAILURE_NOTICE:
//				useComponentMap.remove(type);
//				failedList.add(message.getSrcAddress());
//				findSubstitute(type);
				break;

			case ADDRESS_REPLY:
				AddressReplyMessage replyMessage = (AddressReplyMessage) message;

				sendServiceTimeRequest(type, replyMessage.getAddressList());
				break;

			case DELEGATION: 
				useComponentMap.remove(type);
				searchComponentMap.remove(type);
				this.messageSender.sendMessage(new UseRequestMessage(this.getAddress(), ((DelegationMessage)message).getDelegateAddress(), type, this.getAccessNum()));
				break;

                        case TAT_REPLY:
                            //messageBuffer.clear();
                            break;
		}
	}
}
